/** @file    JSpaceCommBundle.cpp
 *  @author  Alessandro Polo
 *  @version $Id: JSpaceCommBundle.cpp 2891 2010-08-12 01:58:59Z alex $
 *  @brief
 * File containing methods for the wosh::devices::JSpaceCommBundle class.
 * The header for this class can be found in JSpaceCommBundle.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include "JSpaceCommBundle.h"
 #include "JSpaceCommImpl.h"
 #include <core/ObjectAllocator.h>
 #include <core/MethodsCommon.h>
 #include <core/SystemError.h>
 #include <core/MessageFilter.h>
 #include <core/DeviceManager.h>
 #include <core/UserManager.h>
 #include <core/WoshKernel.h>
 /*
 #include <core/Fact.h>
 #include <core/ThreadManager.h>
 #include <core/MessageFilter.h>
 #include <core/MessageProcessingCommon.h>
 #include <core/SystemError.h>
 #include <framework/communication/TextMessage.h>
*/

using namespace std;
namespace wosh {
 namespace devices {

 WOSH_REGISTER(wosh::devices::JSpaceCommBundle, "wosh::interfaces::Device::JSpaceComm", _JSpaceComm_VERSION, _static_JSpaceCommBundle1 )
 WOSH_REGISTER(wosh::devices::JSpaceCommBundle, "wosh::interfaces::services::CommunicatorService", _JSpaceComm_VERSION, _static_JSpaceCommBundle2 )
 WOSH_REGISTER(wosh::devices::JSpaceCommBundle, "wosh::BundleGeneric", _JSpaceComm_VERSION, _static_JSpaceCommBundle3 )

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

JSpaceCommBundle::JSpaceCommBundle()
	: BundleGeneric() {
	BundleGeneric::setName( _JSpaceComm_NAME, false );
	Properties.update( _KEY_Version, _JSpaceComm_VERSION );
	Log(LOG_DEBUG, " Configuring JSpaceComm worker.." );
	this->devWorker = new JSpaceCommImpl(*this);
	this->devWorker->setJSpaceCommListener(this);
	this->devState = wosh::interfaces::Device::UNKNOWN;

	Log(LOG_DEBUG, " Setting default properties and permissions.." );
	Properties.set( _CommunicatorService_KEY_ChannelType,	(int)Notification::TYPE_TEXTUAL, Permission(Permission::Read) );
	Properties.set( _CommunicatorService_KEY_ChannelLink,	(int )Communicator::LINK_SYNCH, Permission(Permission::Read) );
	Properties.set( _CommunicatorService_KEY_Cost,			0.0, Permission::RW_R__R__() );
	Properties.set( _CommunicatorService_KEY_Priority,		50, Permission::RW_RW_R__() );
	Properties.set( _CommunicatorService_KEY_Reliability,	70, Permission::RW_RW_R__() );
	Properties.set( _CommunicatorService_KEY_Protocol,		(int)Notification::PROTOCOL_SHELL + (int)Notification::PROTOCOL_HUMAN, Permission::RW_R__R__() );


	Log(LOG_DEBUG, " Registering methods.." );
	MethodMessageResponse* mmSendMessageToUser = Methods.create<MethodMessageResponse>( _CommunicatorService_METHOD_sendmessage, "send message to (wosh) username", Permission(Permission::RX) );
	mmSendMessageToUser->setMethod( this, (MethodMessageResponsePtr)&JSpaceCommBundle::mmDoSendMessageToUser );

	setBundleState(Bundle::STATE_CREATED, false);
 }


JSpaceCommBundle::~JSpaceCommBundle() {
	Log(LOG_INFO, " Destroying.." );
	if ( isBundleRunning() ) {
		Log(LOG_WARNING, "~JSpaceCommBundle() : Destroying while Bundle is running! Trying to stop.." );
		bundleStop();
	 }
	if ( this->devWorker->isThreadRunning() ) {
		Log(LOG_WARNING, "~JSpaceCommBundle() : Destroying while RUNNING! Trying to stop.." );
		this->devWorker->quitThread();
		this->devWorker->waitThread(30000);
	 }
	delete this->devWorker; this->devWorker = NULL;
	Log(LOG_VERBOSE, ":~JSpaceCommBundle() : Destroyed." );
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL

WRESULT JSpaceCommBundle::bundleStart() {
	if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STATE_STARTING) ) return WRET_ERR_WRONG_STATE;

	WRESULT ret = BundleGeneric::start_SynchThread( this->devWorker );

	Response* response = new Response( _METHOD_ListProperties, Properties.toTable(false) );
	Message* message = new Message(response);
	message->setSource(this);
	message->getDestination().fromString("*:Communication");
	signMessage(message);
	BusCore.postMessage(message);

	Log(LOG_DEBUG, ":bundleStart() : Registering as Device.." );
	wosh::Device* myself = new wosh::Device(*this);
	myself->getInterfaces() = this->Interfaces;
	WoshKernel::getInstance()->devices().registerDevice( myself );

	// BUNDLE-STATE (STARTED) will be updated async by WORKER, through call: IThreadListener::thread_event()
	return ret;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT JSpaceCommBundle::bundleStop() {
	if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STATE_STOPPING) ) return WRET_ERR_WRONG_STATE;

	WRESULT ret = BundleGeneric::stop_SynchThread( this->devWorker );

	if ( WSUCCEEDED(ret) ) {
		Log(LOG_VERBOSE, ":bundleStart() : UnRegistering as Device.." );
		WoshKernel::getInstance()->devices().unRegisterDevice( this->getName() );
	 }
	// BUNDLE-STATE (STOPPED) will be updated async by WORKER, through call: IThreadListener::thread_event()
	return ret;
 }

WRESULT JSpaceCommBundle::bundleDiagnostic() {
	Log(LOG_VERBOSE, ":bundleDiagnostic() : Starting JSpaceCommBundle-Diagnostic" );

	WRESULT ret = BundleGeneric::bundleDiagnostic();

	Log(LOG_INFO, ":bundleDiagnostic() : Finished JSpaceCommBundle-Diagnostic [%d]", ret );
	return ret;
 }

////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////// PROCESS BUS MESSAGES

void JSpaceCommBundle::busMessage( const Message& message, const Bus* source ) {
	if ( message.isEmpty() ) return;
	if ( message.getContent()->isNotification() ) {
		if ( message.getDestination().isBroadcast() ) return;
		if ( !MessageFilter::isMessageTarget(message, this) ) return;
		if ( !isBundleRunning() ) return;
		const Notification* notif = message.getContent()->asNotification();
		WRESULT sent = notify( notif );
		Message* reply = message.replyTo( new Response( _CommunicatorService_RESPONSE_notif_sent, new Variant(notif->getID()), sent ) );
		signMessage(reply);
		BusCore.postMessage(reply);
		if ( WSUCCEEDED(sent) ) {
			Message* reply2 = message.replyTo( new Response( _CommunicatorService_RESPONSE_notif_read, new Variant(notif->getID()) ) );
			signMessage(reply2);
			BusCore.postMessage(reply2);
		 }
	 }
	DeviceManager::busMessageForBundleDevices( message, source, this, this->Methods, this->Log, this->BusCore, this->BusDevices );
//	BundleGeneric::busMessage(message, source);
 }

//////////////////////////////////////////////////////////////////////////////////////// PROCESS BUS MESSAGES
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENTS

bool JSpaceCommBundle::updatingProperty( bool& do_update, const Variant& value_proposed, Property& property_current, const PropertiesProvider* source ) {
	if ( property_current.getKey() == _KEY_type ) {
	 }
	return BundleGeneric::updatingProperty(do_update, value_proposed, property_current, source);
 }

///////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////// COMMUNICATOR INTERFACE

bool JSpaceCommBundle::isUserReachable( const string& wosh_username ) {
	return false;
 }

WRESULT JSpaceCommBundle::sendMessageToUser( const string& wosh_username, const string& message ) {
	WRESULT ret = sendMessageTo(wosh_username, message );
	return ret;
 }

WRESULT JSpaceCommBundle::sendMessageTo( const string& user_phonenumber, const string& message ) {
	WRESULT ret = this->devWorker->sendMessage(message);
	if ( WFAILED(ret) )
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "sendMessageTo", "FAILED#%d Sending SMS to %s", ret, user_phonenumber.c_str() );
	else
		Log(LOG_INFO, "sendMessageTo(%s, **) SMS message sent", user_phonenumber.c_str() );
	return ret;
 }

WRESULT JSpaceCommBundle::notify( const Notification* notification, const Message* message ) {
	if ( notification == NULL ) return WRET_ERR_PARAM;
	WRESULT ret = WRET_OK;
	if ( notification->isForAnyone() || notification->getRecipent_User().size() == 0 ) {
		// should iterate when group-select or/and ?
		ret = WRET_ERR_INTERNAL;
	 }
	else {
		ret = sendMessageToUser(notification->getRecipent_User(), notification->getPlain() );
	 }
	return ret;
 }
 
////////////////////////////////////////////////////////////////////////////////////// COMMUNICATOR INTERFACE
//////////////////////////////////////////////////////////////////////////////////////////////////////////////


//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////// METHODS

Response* JSpaceCommBundle::mmDoSendMessageToUser( const Message* requestMessage ) {
	if ( requestMessage == NULL || requestMessage->isEmpty() ) return NULL;
	if ( !requestMessage->getContent()->isRequest() ) return NULL;
	const Request* request = requestMessage->getContent()->asRequest();
	Variant dUsername, dMessage;
	request->extractArguments(dUsername, dMessage);
	if ( !dUsername.isStringNotEmpty() || !dMessage.isStringNotEmpty() )
		return request->replyResponse(WRET_ERR_PARAM, "Invalid arguments [username, message]");
	std::string sender = requestMessage->getSecurityToken()->getUsername();
	Log(LOG_VERBOSE, "mmDoSendMessageToUser(%s) : Sending %d chars by %s", dUsername.asString().c_str(), dMessage.asString().size(), sender.c_str() );
	WRESULT ret = sendMessageToUser( dUsername.asString(), dMessage.asString() );
	if ( WSUCCEEDED(ret) )
		return request->replyResponse(WRET_OK, "Sent!" );
	return request->replyResponse(ret, "Internal Error!");
 }

///////////////////////////////////////////////////////////////////////////////////////////////////// METHODS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////


 }; // namespace devices
}; // namespace wosh
